Skip to content

fix: prevent stale RequestInit body replay in fetch-cache shim#2

Open
JaredStowell wants to merge 1 commit intomainfrom
codex/fix-stale-_ogbody-reuse-vulnerability
Open

fix: prevent stale RequestInit body replay in fetch-cache shim#2
JaredStowell wants to merge 1 commit intomainfrom
codex/fix-stale-_ogbody-reuse-vulnerability

Conversation

@JaredStowell
Copy link
Owner

Motivation

  • Prevent a security bug where serializeBody() stashes an _ogBody on a caller-supplied RequestInit and stripNextFromInit() unconditionally restores it, allowing a reused RequestInit to replay a stale body across unrelated requests.

Description

  • Clear internal restoration metadata on each serializeBody() run by deleting _ogBody and adding a _restoreBodyFromOg marker on the local RequestInit wrapper to avoid carrying stale state across calls in packages/vinext/src/shims/fetch-cache.ts.
  • Only set _restoreBodyFromOg when the body actually requires restoration (the ReadableStream.tee() case) and write _ogBody to the local wrapper instead of unconditionally mutating the caller object.
  • Update stripNextFromInit() to restore rest.body only when _restoreBodyFromOg is true, preventing unconditional replay of previously-stashed bodies.
  • Add regression tests in tests/fetch-cache.test.ts that exercise reusing a RequestInit without a body and reusing one with an unsupported body shape to ensure stale bodies are not replayed.

Testing

  • Ran the targeted test file with pnpm vitest run tests/fetch-cache.test.ts and the suite passed: 60 tests in tests/fetch-cache.test.ts succeeded.
  • The new regression tests assert that when a RequestInit is reused after removing body or replacing it with an unsupported shape, the outgoing fetch does not contain a stale body and the behavior is correct (tests passed).
  • No other test suites were run locally; CI will run the full test matrix (lint, typecheck, Vitest full suite, Playwright E2E) on the PR.

Codex Task

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4f0fd62456

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// Restore the original body if it was stashed by serializeBody (e.g. after
// consuming a ReadableStream for cache key generation).
if (_ogBody !== undefined) {
if (_restoreBodyFromOg && _ogBody !== undefined) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore captured body for non-stream requests

Gating restoration on _restoreBodyFromOg drops the _ogBody snapshot for string/Blob/Uint8Array/FormData bodies, so mutating a shared RequestInit after calling fetch() can now alter an in-flight request. In patchedFetch, we await cache-key generation before stripNextFromInit, so a caller doing const p = fetch(..., init); delete init.body; await p; now sends no body, whereas the previous behavior preserved the original body via _ogBody. This is a behavioral regression versus native fetch snapshot semantics and can silently change request payloads when init objects are reused.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant